package org.jeecg.modules.workflow.service.task;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.flowable.engine.HistoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.delegate.event.FlowableCancelledEvent;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.jeecg.common.system.api.ISysBaseAPI;
import org.jeecg.common.system.vo.DictModel;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.util.json.JsonUtils;
import org.jeecg.common.workflow.vo.definition.process.BpmProcessDefTodoVo;
import org.jeecg.common.workflow.vo.definition.process.BpmProcessDefinitionListReqVO;
import org.jeecg.common.workflow.vo.definition.process.BpmProcessDefinitionRespVO;
import org.jeecg.common.workflow.vo.definition.process.BpmProcessTodoVo;
import org.jeecg.common.workflow.vo.task.instance.*;
import org.jeecg.modules.workflow.convert.task.BpmProcessInstanceConvert;
import org.jeecg.modules.workflow.entity.BpmProcessDefinitionExt;
import org.jeecg.modules.workflow.entity.BpmProcessInstanceExt;
import org.jeecg.modules.workflow.enums.task.BpmProcessInstanceDeleteReasonEnum;
import org.jeecg.modules.workflow.enums.task.BpmProcessInstanceResultEnum;
import org.jeecg.modules.workflow.enums.task.BpmProcessInstanceStatusEnum;
import org.jeecg.modules.workflow.framework.core.event.BpmProcessInstanceResultEventPublisher;
import org.jeecg.modules.workflow.mapper.BpmProcessInstanceExtMapper;
import org.jeecg.modules.workflow.service.definition.BpmProcessDefinitionService;
import org.jeecg.modules.workflow.service.task.dto.BpmProcessInstanceCreateReqDTO;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.util.*;

import static org.jeecg.common.exception.util.ServiceExceptionUtil.exception;
import static org.jeecg.common.util.collection.CollectionUtils.convertList;
import static org.jeecg.modules.workflow.enums.ErrorCodeConstants.*;

/**
 * 流程实例 Service 实现类
 * <p>
 * ProcessDefinition & ProcessInstance & Execution & Task 的关系：
 * 1. https://blog.csdn.net/bobozai86/article/details/105210414
 * <p>
 * HistoricProcessInstance & ProcessInstance 的关系：
 * 1.https://my.oschina.net/843294669/blog/719024
 * 简单来说，前者 = 历史 + 运行中的流程实例，后者仅是运行中的流程实例
 *
 * @author 芋道源码
 */
@Service
@Validated
@Slf4j
public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService {

    @Resource
    private RuntimeService runtimeService;
    @Resource
    private HistoryService historyService;

    @Resource
    @Lazy // 解决循环依赖
    private BpmTaskService taskService;

    @Resource
    private BpmProcessDefinitionService processDefinitionService;

    @Resource
    private BpmProcessInstanceResultEventPublisher processInstanceResultEventPublisher;

    @Resource
    private BpmProcessInstanceExtMapper processInstanceExtMapper;

    @Autowired
    BpmProcessInstanceConvert bpmProcessInstanceConvert;

    @Autowired
    private BpmProcessDefinitionService bpmDefinitionService;

    @Autowired
    private ISysBaseAPI sysBaseApi;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String createProcessInstance(String userId, BpmProcessInstanceCreateReqVO createReqVO) {
        // 获得流程定义
        ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId());
        // 发起流程
        return createProcessInstance0(userId, definition, createReqVO.getVariables(), null);
    }

    @Override
    public String createProcessInstance(String userId, BpmProcessInstanceCreateReqDTO createReqDTO) {
        // 获得流程定义
        ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey());
        // 发起流程
        return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey());
    }

    private String createProcessInstance0(String userId, ProcessDefinition definition,
                                          Map<String, Object> variables, String businessKey) {
        // 校验流程定义
        if (definition == null) {
            throw exception(PROCESS_DEFINITION_NOT_EXISTS);
        }
        if (definition.isSuspended()) {
            throw exception(PROCESS_DEFINITION_IS_SUSPENDED);
        }

        // 创建流程实例
        ProcessInstance instance = runtimeService.startProcessInstanceById(definition.getId(), businessKey, variables);
        // 设置流程名字
        runtimeService.setProcessInstanceName(instance.getId(), definition.getName());

        // 补全流程实例的拓展表
        BpmProcessInstanceExt bpmProcessInstanceExt = new BpmProcessInstanceExt();
        bpmProcessInstanceExt.setProcessInstanceId(instance.getId());
        bpmProcessInstanceExt.setFormVariables(JsonUtils.toJsonString(variables));
        bpmProcessInstanceExt.setUpdateBy(getUserName());
        bpmProcessInstanceExt.setUpdateTime(new Date());
        processInstanceExtMapper.updateByProcessInstanceId(bpmProcessInstanceExt);

        // 添加初始的评论 TODO 芋艿：在思考下
//        Task task = taskService.createTaskQuery().processInstanceId(instance.getId()).singleResult();
//        if (task != null) {
//            SysUserDO user = userService.getUser(userId);
//            Assert.notNull(user, "用户({})不存在", userId);
//            String type = "normal";
//            taskService.addComment(task.getId(), instance.getProcessInstanceId(), type,
//                    String.format("%s 发起流程申请", user.getNickname()));
//        }
        return instance.getId();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void cancelProcessInstance(String userId, BpmProcessInstanceCancelReqVO cancelReqVO) {
        // 校验流程实例存在
        ProcessInstance instance = getProcessInstance(cancelReqVO.getId());
        if (instance == null) {
            throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS);
        }
        // 只能取消自己的
        if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) {
            throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF);
        }

        // 通过删除流程实例，实现流程实例的取消
        runtimeService.deleteProcessInstance(cancelReqVO.getId(),
                BpmProcessInstanceDeleteReasonEnum.CANCEL_TASK.format(cancelReqVO.getReason()));
    }

    @Override
    public void deleteProcessInstance(String id, String reason) {
        runtimeService.deleteProcessInstance(id, reason);
    }

    @Override
    public IPage<BpmProcessInstancePageItemRespVO> getMyProcessInstancePage(String userId,
                                                                            BpmProcessInstanceMyPageReqVO pageReqVO) {
        // 通过 BpmProcessInstanceExt 表，先查询到对应的分页
        IPage<BpmProcessInstanceExt> pageResult = processInstanceExtMapper.selectPage(userId, pageReqVO);
        if (null == pageResult) {
            return new Page<>();
        }

        // 获得流程 Task Map
        List<String> processInstanceIds = convertList(pageResult.getRecords(), BpmProcessInstanceExt::getProcessInstanceId);
        Map<String, List<Task>> taskMap = taskService.getTaskMapByProcessInstanceIds(processInstanceIds);
        // 转换返回
        return bpmProcessInstanceConvert.convertPage(pageResult, taskMap);
    }

    @Override
    public BpmProcessInstanceRespVO getProcessInstanceVO(String id) {
        // 获得流程实例
        HistoricProcessInstance processInstance = getHistoricProcessInstance(id);
        if (processInstance == null) {
            return null;
        }
        BpmProcessInstanceExt processInstanceExt = processInstanceExtMapper.selectByProcessInstanceId(id);
        Assert.notNull(processInstanceExt, "流程实例拓展({}) 不存在", id);

        // 获得流程定义
        ProcessDefinition processDefinition = processDefinitionService.getProcessDefinition(
                processInstance.getProcessDefinitionId());
        Assert.notNull(processDefinition, "流程定义({}) 不存在", processInstance.getProcessDefinitionId());
        BpmProcessDefinitionExt processDefinitionExt = processDefinitionService.getProcessDefinitionExt(
                processInstance.getProcessDefinitionId());
        Assert.notNull(processDefinitionExt, "流程定义拓展({}) 不存在", id);
        String bpmnXml = processDefinitionService.getProcessDefinitionBpmnXML(processInstance.getProcessDefinitionId());

        // 获得 User
        LoginUser startUser = sysBaseApi.getUserById(processInstance.getStartUserId());

        // 拼接结果
        return bpmProcessInstanceConvert.convert2(processInstance, processInstanceExt,
                processDefinition, processDefinitionExt, bpmnXml, startUser);
    }

    @Override
    public List<ProcessInstance> getProcessInstances(Set<String> ids) {
        return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list();
    }

    @Override
    public ProcessInstance getProcessInstance(String id) {
        return runtimeService.createProcessInstanceQuery().processInstanceId(id).singleResult();
    }

    /**
     * 获得历史的流程实例
     *
     * @param id 流程实例的编号
     * @return 历史的流程实例
     */
    @Override
    public HistoricProcessInstance getHistoricProcessInstance(String id) {
        return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).singleResult();
    }

    @Override
    public List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids) {
        return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list();
    }

    @Override
    public void createProcessInstanceExt(org.flowable.engine.runtime.ProcessInstance instance) {
        // 获得流程定义
        ProcessDefinition definition = processDefinitionService.getProcessDefinition2(instance.getProcessDefinitionId());
        // 插入 BpmProcessInstanceExt 对象
        BpmProcessInstanceExt instanceExtDO = bpmProcessInstanceConvert.convert(instance);
        instanceExtDO.setCategory(definition.getCategory());
        instanceExtDO.setStatus(BpmProcessInstanceStatusEnum.RUNNING.getStatus());
        instanceExtDO.setResult(BpmProcessInstanceResultEnum.PROCESS.getResult());
        instanceExtDO.setCreateBy(getUserName());
        instanceExtDO.setCreateTime(new Date());
        processInstanceExtMapper.insert(instanceExtDO);
    }

    @Override
    public void updateProcessInstanceExt(org.flowable.engine.runtime.ProcessInstance instance) {
        BpmProcessInstanceExt instanceExtDO = bpmProcessInstanceConvert.convert(instance);
        instanceExtDO.setUpdateBy(getUserName());
        instanceExtDO.setUpdateTime(new Date());
        processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO);
    }

    @Override
    public void updateProcessInstanceExtCancel(FlowableCancelledEvent event) {
        // 判断是否为 Reject 不通过。如果是，则不进行更新.
        // 因为，updateProcessInstanceExtReject 方法，已经进行更新了
        if (BpmProcessInstanceDeleteReasonEnum.isRejectReason((String) event.getCause())) {
            return;
        }

        // 需要主动查询，因为 instance 只有 id 属性
        // 另外，此时如果去查询 ProcessInstance 的话，字段是不全的，所以去查询了 HistoricProcessInstance
        HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId());
        // 更新拓展表
        BpmProcessInstanceExt instanceExtDO = new BpmProcessInstanceExt();
        instanceExtDO.setProcessInstanceId(event.getProcessInstanceId());
        instanceExtDO.setEndTime(new Date()); // 由于 ProcessInstance 里没有办法拿到 endTime，所以这里设置
        instanceExtDO.setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus());
        instanceExtDO.setResult(BpmProcessInstanceResultEnum.CANCEL.getResult());
        instanceExtDO.setUpdateBy(getUserName());
        instanceExtDO.setUpdateTime(new Date());
        processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO);

        // 发送流程实例的状态事件
        processInstanceResultEventPublisher.sendProcessInstanceResultEvent(
                bpmProcessInstanceConvert.convert(this, processInstance, instanceExtDO.getResult()));
    }

    @Override
    public void updateProcessInstanceExtComplete(org.flowable.engine.runtime.ProcessInstance instance) {
        // 需要主动查询，因为 instance 只有 id 属性
        // 另外，此时如果去查询 ProcessInstance 的话，字段是不全的，所以去查询了 HistoricProcessInstance
        HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId());
        // 更新拓展表
        BpmProcessInstanceExt instanceExtDO = bpmProcessInstanceConvert.convert(instance);
        instanceExtDO.setEndTime(new Date()); // 由于 ProcessInstance 里没有办法拿到 endTime，所以这里设置
        instanceExtDO.setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus());
        instanceExtDO.setResult(BpmProcessInstanceResultEnum.APPROVE.getResult()); // 如果正常完全，说明审批通过
        instanceExtDO.setUpdateBy(getUserName());
        instanceExtDO.setUpdateTime(new Date());
        processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO);

        // 发送流程被通过的消息
        //messageService.sendMessageWhenProcessInstanceApprove(bpmProcessInstanceConvert.convert2(instance));

        // 发送流程实例的状态事件
        processInstanceResultEventPublisher.sendProcessInstanceResultEvent(
                bpmProcessInstanceConvert.convert(this, processInstance, instanceExtDO.getResult()));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateProcessInstanceExtReject(String id, String comment) {
        // 需要主动查询，因为 instance 只有 id 属性
        ProcessInstance processInstance = getProcessInstance(id);
        // 删除流程实例，以实现驳回任务时，取消整个审批流程
        deleteProcessInstance(id, StrUtil.format(BpmProcessInstanceDeleteReasonEnum.REJECT_TASK.format(comment)));

        // 更新 status + result
        // 注意，不能和上面的逻辑更换位置。因为 deleteProcessInstance 会触发流程的取消，进而调用 updateProcessInstanceExtCancel 方法，
        // 设置 result 为 BpmProcessInstanceStatusEnum.CANCEL，显然和 result 不一定是一致的
        BpmProcessInstanceExt instanceExtDO = new BpmProcessInstanceExt();
        instanceExtDO.setProcessInstanceId(id);
        instanceExtDO.setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus());
        instanceExtDO.setResult(BpmProcessInstanceResultEnum.REJECT.getResult());
        instanceExtDO.setUpdateBy(getUserName());
        instanceExtDO.setUpdateTime(new Date());
        processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO);

        // 发送流程被不通过的消息
        //messageService.sendMessageWhenProcessInstanceReject(bpmProcessInstanceConvert.convert(processInstance, comment));

        // 发送流程实例的状态事件
        processInstanceResultEventPublisher.sendProcessInstanceResultEvent(
                bpmProcessInstanceConvert.convert(this, processInstance, instanceExtDO.getResult()));
    }


    @Override
    public Map<String, List<BpmProcessTodoVo>> getTodoProInsMap(String userId, BpmProcessDefinitionListReqVO listReqVO) {
        Map<String, List<BpmProcessTodoVo>> resultMap = new HashMap<>();
        List<BpmProcessDefinitionRespVO> processDefinitionList = bpmDefinitionService.getProcessDefinitionList(listReqVO);
        List<BpmProcessDefTodoVo> remainDefList = new ArrayList<>();
        Set<String> categorySet = new HashSet<>();
        Set<String> processNameSet = new HashSet<>();
        for (BpmProcessDefinitionRespVO bpmProcessDefinitionRespVO : processDefinitionList) {
            String defId = bpmProcessDefinitionRespVO.getId();
            Long taskCount = taskService.getTaskCountByUserIdAndDefId(userId, defId);
            // 存在任务的对应实例
            if (taskCount > 0) {
                processNameSet.add(bpmProcessDefinitionRespVO.getName());
                categorySet.add(bpmProcessDefinitionRespVO.getCategory());
                BpmProcessDefTodoVo bpmProcessDefTodoVo = new BpmProcessDefTodoVo();
                BeanUtils.copyProperties(bpmProcessDefinitionRespVO, bpmProcessDefTodoVo);
                bpmProcessDefTodoVo.setTodoTackCount(taskCount);
                bpmProcessDefTodoVo.setCountName(bpmProcessDefTodoVo.getName() + "(" + taskCount + ")");
                remainDefList.add(bpmProcessDefTodoVo);
            }
        }

        List<BpmProcessTodoVo> remainProcessList = new ArrayList<>();
        for (String processName : processNameSet) {
            BpmProcessTodoVo bpmProcessTodoVo = new BpmProcessTodoVo();
            bpmProcessTodoVo.setName(processName);
            Long count = 0L;
            for (BpmProcessDefTodoVo bpmProcessDefTodoVo : remainDefList) {
                if (bpmProcessDefTodoVo.getName().equals(processName)) {
                    bpmProcessTodoVo.setCategory(bpmProcessDefTodoVo.getCategory());
                    count += bpmProcessDefTodoVo.getTodoTackCount();
                }
            }
            bpmProcessTodoVo.setTodoTackCount(count);
            bpmProcessTodoVo.setCountName(bpmProcessTodoVo.getName() + "(" + count + ")");
            remainProcessList.add(bpmProcessTodoVo);
        }


        // 字典code转字典value
        List<DictModel> bpmModelCategory = sysBaseApi.getDictItems("bpm_model_category");
        Map<String, String> dictMap = new HashMap<>();
        for (DictModel dictModel : bpmModelCategory) {
            dictMap.put(dictModel.getValue(), dictModel.getText());
        }
        Set<String> keySet = dictMap.keySet();
        for (String category : categorySet) {
            // 防止非字典项进入
            if (!keySet.contains(category)) {
                continue;
            }
            List<BpmProcessTodoVo> categoryResultList = new ArrayList<>();
            for (BpmProcessTodoVo bpmProcessTodoVo : remainProcessList) {
                if (bpmProcessTodoVo.getCategory().equals(category)) {
                    categoryResultList.add(bpmProcessTodoVo);
                }
            }
            resultMap.put(dictMap.get(category), categoryResultList);
        }
        return resultMap;
    }

    private String getUserName() {
        LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
        return sysUser.getRealname();
    }
}
